<?php
/**
 * 模型基类,提供ORM功能
 * @author Leo Ning 2010-09-06
 */

abstract class Model {
	const LEFT = 'LEFT'; //左连接	
	const RIGHT = 'RIGHT'; //右连接
	const INNER = 'INNER'; //内连接
	/**
	 * 数据源
	 * @var DataSource
	 */
	private $_ds = NULL;
	private $_table = ''; //表名
	private $_ftable = ''; //表全名,添加前缀后的表名
	private $_fields = array ();
	private $_pk = ''; //主键
	private $_all = '*'; //全部字段
	private $_entity_name;
	
	protected $retrieveit = true; //读取数据
	protected $countit = false; //查询总数
	protected $countfield; //记数字段
	protected $whereString = ''; //条件语句
	protected $retrieveOptions = array (); //查询选项
	protected $joins = array (); //连接
	protected $total = 0; //总数
	

	public function __construct($datasource = null, $table = '') {
		$this->_ds = $datasource;
		//获取表名
		$this->_entity_name = preg_replace ( '/model$/i', '', get_class ( $this ) );
		if (! empty ( $table )) {
			$this->_table = $table;
		} else {
			$this->_table = strtolower ( $this->_entity_name );
		}
		if ($datasource != null) {
			$this->_ftable = $this->_ds->fullname ( $this->_table );
		}
		$this->schema ();
	}
	/**
	 * 设置数据源
	 * @param DataSource $dataSource
	 */
	public function setDataSource($dataSource) {
		$this->_ds = $dataSource;
		if (! empty ( $this->_ds )) {
			$this->_ftable = $this->_ds->fullname ( $this->_table );
		}
	}
	///////////////////////////////////////////////////////////////////////////////////////
	/**
	 * 从数据库取出主键对应的数据	 
	 * @return Model
	 */
	public function read() {
		if (empty ( $this->whereString )) {
			$pk = array_shift ( $this->_pk );
			$data [$pk] = $this->{$pk};
			if (! is_null ( $data [$pk] )) {
				$this->where ( $data );
			}
		}
		$rst = $this->retrieve ( '*', 0 );
		if (! empty ( $rst )) {
			$this->bind ( $rst );
			return $this;
		}
		return false;
	}
	/**
	 * 检索数据.
	 * @see retrieve()
	 * @param mixed $fields 检索的字段
	 * @param int   $index  检索第几条数据	
	 * @return ResultSet 结果集.
	 */
	public function select($fields = '*', $index = null) {
		return $this->retrieve ( $fields, $index );
	}
	/**
	 * 保存模型对应的数据,$data 为key/value的映射的记录形势.可以是多条记录.
	 * 如果$data的$key中包括主键,则修改记录,反之新增记录.如果$data为空,
	 * 则使用本实例的属性对应的值.
	 *
	 * @param array $data 要保存的数据.
	 * @return array 保存后的数据.
	 */
	public function save($data = array()) {
		if (empty ( $data )) {
			$data = $this->data ();
		}
		if (! empty ( $this->whereString )) { //有条件的保存,此时同update方法
			return $this->update ( $data );
		}
		$pk = $this->_pk [0];
		if ((! empty ( $pk ) && isset ( $data [$pk] ) && ! empty ( $data [$pk] ))) {
			return $this->update ( $data, $data [$pk] );
		}
		return $this->insert ( $data );
	}
	/**
	 * 修改数据记录$data 为key/value的映射的记录形势.
	 * 如果$data为空,则使用本实例的属性对应的值.
	 * @param array $data 要修改的记录
	 * @param array $id 要修改记录的ID或条件.
	 * @return mixed 修改成功为原始数据,否则为false
	 */
	public function update($data = null, $id = null) {
		$data = is_null ( $data ) ? $this->data () : $data;
		if (! empty ( $data )) {
			if (! is_null ( $id ) && ! is_array ( $id )) {
				$_data = $data;
				unset ( $_data [$this->_pk [0]] );
				$update_sql = $this->update_sql ( $_data, $id );
			
			} else if (is_array ( $id )) {
				$this->where ( $id );
				$update_sql = $this->update_sql ( $data );
			
			} else {
				$update_sql = $this->update_sql ( $data );
			}
			
			$rst = empty ( $update_sql ) ? false : $this->execute ( $update_sql );
			
			if ($rst === false) {
				$data = false;
			}
		}
		return $data;
	}
	/**
	 * 新增记录.$data 为key/value的映射的记录形势.可以是多条记录.
	 * 如果$data为空,则使用本实例的属性对应的值.
	 * @param array $data
	 * @return mixed 新增的数据.新增失败时返回false
	 */
	public function insert($data = array()) {
		if (empty ( $data )) {
			$data = $this->data ();
		}
		$insert_sql = $this->insert_sql ( $data );
		$rst = $this->execute ( $insert_sql );
		
		if ($rst > 0 && ! empty ( $this->_pk )) {
			$data [array_pop ( $this->_pk )] = $this->_ds->last_insert_id ();
			$this->bind ( $data );
		} else {
			$data = false;
		}
		return $data;
	}
	/**
	 * 删除符合条件的记录,如果没有通过where指定条件,则优先使用主键,如果没有为主键属性赋值,则删除所有记录.
	 * @return int 影响的记录数
	 */
	public function delete() {
		if (empty ( $this->whereString )) {
			$pk = array_shift ( $this->_pk );
			$data [$pk] = $this->{$pk};
			if (! is_null ( $data [$pk] )) {
				$this->where ( $data );
			}
		}
		$sql = "DELETE FROM `{$this->_ftable}`";
		$sql .= empty ( $this->whereString ) ? '' : " WHERE {$this->whereString}";
		return $this->execute ( $sql );
	}
	
	/**
	 * 记录是否存在,条件由WHERE输入
	 *
	 * @return boolean
	 */
	public function exist() {
		$total = $this->count ( false )->retrieve ();
		return $total > 0;
	}
	/**
	 * 最后一次生成的ID
	 *
	 * @return int
	 */
	public function lastid() {
		return $this->_ds->last_insert_id ();
	}
	/**
	 * 符合条件的记录数
	 *
	 * @param boolean $condition 条件
	 * @return Model
	 */
	public function count($retrieve = false, $field = '*') {
		$this->total = 0;
		$this->countit = true;
		$this->retrieveit = $retrieve;
		$this->countfield = $field == "*" ? array_pop ( $this->_pk ) : $field;
		return $this;
	}
	/**
	 * 生成条件语句
	 *
	 * @param array $conditions
	 * @return Model
	 */
	public function where($conditions) {
		if (is_array ( $conditions ) && ! empty ( $conditions )) {
			$this->whereString = $this->where_sql ( $conditions );
		}
		return $this;
	}
	
	/**
	 * 分页查询数据.
	 *
	 * @param int $limit 分页大小,默认为null
	 * @param int $start 分页偏移,默认为null
	 * @return Model
	 */
	public function limit($limit = null, $start = 0) {
		if (is_numeric ( $start ) && is_numeric ( $limit )) {
			$this->retrieveOptions ['limit'] = $limit;
			$this->retrieveOptions ['start'] = $start;
		}
		return $this;
	}
	/**
	 * 分组查询
	 *
	 * @param string $groupBy 分组条件
	 * @return Model
	 */
	public function groupBy($groupBy) {
		$this->retrieveOptions ['group'] = $groupBy;
		return $this;
	}
	/**
	 * 排序
	 *
	 * @param string $orderBy 排序语句
	 * @return Model
	 */
	public function orderBy($orderBy) {
		$this->retrieveOptions ['order'] = $orderBy;
		return $this;
	}
	/**
	 * 按$field升序排列
	 * @param string $field
	 * @return Model
	 */
	public function sortAsc($field) {
		$this->retrieveOptions ['order'] = $field . " ASC";
		return $this;
	}
	/**
	 * 按$field降序排列
	 * @param string $field
	 * @return Model
	 */
	public function sortDesc($field) {
		$this->retrieveOptions ['order'] = $field . " DESC";
		return $this;
	}
	/**
	 * HAVING 子句
	 *
	 * @param string $having HAVING 子句
	 * @return Model
	 */
	public function having($having) {
		$this->retrieveOptions ['having'] = $having;
		return $this;
	}
	
	/**
	 * 连接查询
	 *
	 * @param Model $entity 连接的实体
	 * @param string $on 连接条件
	 * @param string $dir 连接方向,默认为DbTable::LEFT左连接
	 * @return Model
	 */
	public function join($table, $on, $dir = Model::LEFT) {
		if ($table instanceof Model) {
			$j_tbl_name = $table->fullname;
			$j_entity = $table->entity;
		} else {
			$j_tbl_name = $this->_ds->fullname ( $table );
			$j_entity = strtoupper ( $table );
		}
		$sql = $dir . " JOIN `{$j_tbl_name}` AS `{$j_entity}` ON ({$on})";
		$this->joins [] = $sql;
		return $this;
	}
	
	/**
	 * 检索数据.
	 *
	 * @param mixed $fields 检索的字段
	 * @param int   $index  检索第几条数据	
	 * @return ResultSet 结果集.
	 */
	public function retrieve($fields = '*', $index = null) {
		
		$fields = gettype ( $fields ) == 'string' ? $fields : implode ( ',', array_unique ( $fields ) );
		
		$fields = $fields == "*" ? $this->_all : $fields;
		
		$tbl_name = $this->_ftable;
		
		$options = $this->retrieveOptions;
		if (! empty ( $options )) {
			extract ( $options );
			unset ( $options );
		}
		$entities = "`{$tbl_name}` AS `{$this->_entity_name}`";
		if (! empty ( $this->joins )) {
			foreach ( $this->joins as $join ) {
				$entities = "({$entities} {$join})";
			}
		}
		$condition = $this->whereString;
		$sql = "SELECT {$fields} FROM $entities";
		if ($this->countit) {
			$sql_total = "SELECT COUNT(" . $this->countfield . ") as total FROM $entities";
		}
		$sql .= empty ( $condition ) ? '' : " WHERE {$condition}";
		if ($this->countit) {
			$sql_total .= empty ( $condition ) ? '' : " WHERE {$condition}";
		}
		$sql .= (isset ( $group ) && ! empty ( $group )) ? " GROUP BY {$group}" : '';
		if ($this->countit) {
			$sql_total .= (isset ( $group ) && ! empty ( $group )) ? " GROUP BY {$group}" : '';
		}
		$sql .= (isset ( $order ) && ! empty ( $order )) ? " ORDER BY {$order}" : '';
		if ($this->countit) {
			$sql_total .= (isset ( $order ) && ! empty ( $order )) ? " ORDER BY {$order}" : '';
		}
		$sql .= (isset ( $having ) && ! empty ( $having )) ? " HAVING {$having}" : '';
		if ($this->countit) {
			$sql_total .= (isset ( $having ) && ! empty ( $having )) ? " HAVING {$having}" : '';
		}
		if (isset ( $limit ) && is_int ( $limit )) {
			$start = isset ( $start ) && is_int ( $start ) ? $start : 0;
			$options = array ('start' => $start, 'limit' => $limit );
		} else {
			$options = array ();
		}
		if ($this->countit) {
			$total = $this->query ( $sql_total, $options );
			if (! empty ( $total )) {
				$this->total = $total->fieldAt ( "total", 0, 0 );
			}
			$this->countit = false;
		}
		if ($this->retrieveit) {
			$records = $this->query ( $sql, $options );
			if (is_numeric ( $index )) {
				if (! empty ( $records ) && $records->rowCount () > 0) {
					$records->countTotal = $this->total;
					return $records->at ( $index );
				} else {
					return array ();
				}
			} else {
				if (! empty ( $records )) {
					$records->countTotal = $this->total;
				}
				return $records;
			}
		} else {
			$this->retrieveit = true;
			return $this->total;
		}
	}
	/**
	 * 得到一条记录中的一个字段值
	 * 
	 * @param string $field
	 * @return mixed false时为找到值
	 */
	public function get($field) {
		$rst = $this->retrieve ( $field, 0 );
		if (empty ( $rst )) {
			return false;
		} else {
			return $rst [$field];
		}
	}
	/**
	 * 将结果集连接在一起
	 * @param string $field  要连接在一起的字段
	 * @param string $glue  连接字符
	 * @return string
	 */
	public function field_join($field, $glue = ',') {
		$rst = $this->retrieve ( $field );
		$pieces = array ();
		foreach ( $rst as $r ) {
			$pieces [] = $r [$field];
		}
		return join ( $glue, $pieces );
	}
	/**
	 * 用结果集形成key/value形式的map
	 * @param string $key_field 将作为Key值的字段
	 * @param string $value_field 作为value值的字段
	 * @return array
	 */
	public function field_map($key_field, $value_field) {
		$rst = $this->retrieve ( $key_field . ',' . $value_field );
		$ary = array ();
		foreach ( $rst as $r ) {
			$ary [$r [$key_field]] = $r [$value_field];
		}
		return $ary;
	}
	/**
	 * 执行一条SQL语句.
	 *
	 * @param string $sql 标准的SQL,不包括分页信息.
	 * @param array $options array('start'=>0,'limit'=>page_size)分页信息.
	 * @return ResultSet
	 */
	public function query($sql, $options = array()) {
		if (is_array ( $options ) && isset ( $options ['start'] ) && isset ( $options ['limit'] )) {
			$rst = $this->_ds->queryLimit ( $sql, $options ['start'], $options ['limit'] );
		} else {
			$rst = $this->_ds->query ( $sql );
		}
		$this->joins = array ();
		$this->retrieveOptions = array ();
		$this->whereString = '';
		$this->queryString = $sql;
		$this->duration = 0;
		return $rst;
	}
	/**
	 * 执行一条SQL语句,返回影响的记录条数.
	 *
	 * @param string $sql
	 * @return int
	 */
	public function execute($sql) {
		$this->joins = array ();
		$this->retrieveOptions = array ();
		$this->whereString = '';
		$this->queryString = $sql;
		$this->duration = 0;
		return $this->_ds->execute ( $sql );
	}
	
	///////////////////////////////////////////////////////////////////////////////////////
	/**
	 * 魔术方法获取字段
	 * @param string $name
	 * @return mixed
	 */
	public function __get($name) {
		if ($name == 'fullname') {
			return $this->_ftable;
		} else if ($name == 'entity') {
			return $this->_entity_name;
		} else if (isset ( $this->_fields [$name] )) {
			$field = $this->_fields [$name] ['var'];
			return $this->{$field};
		}
		return null;
	}
	public function __set($name, $value) {
		//TODO 数据校验
		if (isset ( $this->_fields [$name] )) {
			$field = $this->_fields [$name] ['var'];
			$this->{$field} = $value;
		}
	}
	/**
	 * 取得实体map格式的数据
	 * @return array  Model实例map后的数据
	 */
	public function data() {
		$datas = array ();
		foreach ( $this->_fields as $field => $info ) {
			$datas [$field] = $this->{$info ['var']};
		}
		return $datas;
	}
	/**
	 * 将map类型的数据绑定到当前Model
	 * @param array $data map格式,key/value
	 */
	public function bind($data) {
		//TODO 进行类型校验
		foreach ( $this->_fields as $field => $info ) {
			if (isset ( $data [$field] )) {
				$this->{$info ['var']} = $data [$field];
			}
		}
	}
	/**
	 * 最后错误信息
	 * @return string
	 */
	public function last_error_msg() {
		return $this->_ds->last_error_msg ();
	}
	/**
	 * 编译SQL的条件语句.
	 *
	 * @param array $conditions 条件.
	 * @param string $and_or AND 或者 O
	 * @return string 预处理的复合查询条件.
	 * @access private
	 */
	protected function where_sql($conditions, $and_or = '') {
		if (empty ( $conditions ) || ! is_array ( $conditions )) {
			return '';
		}
		$c = array ();
		$first = true;
		foreach ( $conditions as $and_or => $condition ) {
			$and_ors = preg_split ( '/[\s]+/', trim ( $and_or ) );
			$and_ors = count ( $and_ors ) >= 2 ? $and_ors : array ($and_ors [0], '=' );
			$and_or = array_shift ( $and_ors ); //字段
			$op = strtoupper ( join ( " ", $and_ors ) ); //运算符
			if (preg_match ( '/(.*?)([!=><]{1,2})/i', $and_or, $m )) {
				$and_or = $m [1];
				$op = $m [2];
			}
			if (is_array ( $condition )) { //如果是嵌套条件
				if (! is_numeric ( $and_or )) {
					$_and_or = strtoupper ( $and_or );
					if ($_and_or == 'AND' || $_and_or == "OR") {
						$c [] = $_and_or;
					}
				}
				$c [] = "(";
				$c [] = $this->where_sql ( $condition, $and_or );
				$c [] = ")";
			} else {
				$left_val = isset ( $this->_fields [$and_or] ) ? "`{$and_or}`" : $and_or;
				if (! $first) {
					$c [] = 'AND';
				}
				if (is_null ( $condition )) {
					$c [] = $left_val;
					$c [] = 'IS NULL';
				} else if (NOTNULL === $condition) {
					$c [] = $left_val;
					$c [] = 'IS NOT NULL';
				} else {
					$c [] = $left_val . ' ' . $op;
					if ($op == 'IN' || $op == 'NOT IN') {
						$c [] = " ({$condition})";
					} else {
						if (isset ( $this->_fields [$and_or] )) {
							$type = $this->_fields [$and_or] ['type'];
							if ('number' == $type) {
								$c [] = $condition;
							} else {
								$c [] = "'" . $this->_ds->escape ( $condition ) . "'";
							}
						} else {
							$c [] = "'" . $this->_ds->escape ( $condition ) . "'";
						}
					}
				}
				$first = false;
			}
		}
		$first = array_shift ( $c );
		$c = implode ( ' ', $c );
		if (! ($first == 'AND' || $first == 'OR')) {
			$c = $first . ' ' . $c;
		}
		return $c;
	}
	
	//生成insert sql
	protected function insert_sql($data) {
		$fields = array ();
		$values = array ();
		foreach ( $this->_fields as $field => $meta ) {
			if ($meta ['auto_increment'] === true) {
				continue;
			}
			if ($meta ['required'] && ! isset ( $data [$field] ) && is_null ( $meta ['default'] ) && empty ( $meta ['time_update'] )) { //必填字段
				$this->_ds->last_error_msg ( 'The field ' . $field . ' of ' . $this->_entity_name . ' is required' );
				return false;
			}
			$type = $meta ['type'];
			$value = isset ( $data [$field] ) ? $data [$field] : (is_null ( $meta ['default'] ) ? ($type == 'string' ? '' : 0) : $meta ['default']);
			
			$fields [] = '`' . $field . '`';
			
			if (empty ( $value )) {
				if ($meta ['time_update'] == 't') {
					$value = date ( 'Y-m-d H:i:s' );
				} else if ($meta ['time_update'] == 'T') {
					$value = time ();
				} else if ($meta ['time_update'] == 'd') {
					$value = date ( 'Y-m-d' );
				}
			}
			$value = $this->_ds->escape ( $value );
			if ($type == 'number') {
				$values [] = "{$value}";
			} else {
				$values [] = "'{$value}'";
			}
		}
		$fs = join ( ',', $fields );
		$vs = join ( ',', $values );
		
		return "INSERT INTO `{$this->_ftable}` ({$fs}) VALUES ({$vs})";
	}
	//生成update sql
	protected  function update_sql($data, $id = null) {
		$fields = array ();
		if (! is_null ( $id )) {
			$this->where ( array ($this->_pk [0] => $id ) );
		}
		settype ( $data, 'array' );
		$condition = $this->whereString;
		foreach ( $this->_fields as $field => $meta ) {
			//TODO 根据数据类型优化以下操作
			if (isset ( $data [$field] )) {
				$value = $this->_ds->escape ( $data [$field] );
				if ($meta ['type'] == 'number') {
					$fields [] = ! is_null ( $value ) ? '`' . $field . "` = " . $value : "`" . $field . "` =  NULL";
				} else {
					$fields [] = ! is_null ( $value ) ? '`' . $field . "` = '" . $value . "'" : "`" . $field . "` =  NULL";
				}
			} else if (isset ( $data ["&$field"] )) {
				$value = $data ["&$field"];
				$fields [] = ! is_null ( $value ) ? "`" . $field . "` = " . $value : "`" . $field . "` =  NULL";
			} else if (! empty ( $meta ['time_update'] ) && isset ( $meta ['auto_update'] )) {
				$value = $meta ['time_update'] == 't' ? "'" . date ( 'Y-m-d H:i:s' ) . "'" : ($meta ['time_update'] == 'T' ? time () : "'" . date ( 'Y-m-d' ) . "'");
				$fields [] = "`{$field}` = {$value}";
			}
		}
		if (! empty ( $fields )) {
			$fs = join ( ',', $fields );
			return "UPDATE `{$this->_ftable}` SET {$fs} " . (empty ( $condition ) ? '' : " WHERE {$condition}");
		} else {
			$this->_ds->last_error_msg ( 'No fields of ' . $this->_entity_name . ' were to be updated' );
			return false;
		}
	}
	
	//生成表描述
	private function schema() {
		$fields = get_class_vars ( get_class ( $this ) );
		
		foreach ( $fields as $field => $val ) {
			if (! preg_match ( '/^[a-z][a-z_]*(_[adtnsrk]{0,5})?/i', $field )) {
				continue;
			}
			$info = explode ( '_', $field );
			$option = array_pop ( $info );
			if (empty ( $info ) || ! preg_match ( '/[adtnsrk]{1,5}/i', $option )) { //使用默认选项's'
				$info [] = $option;
				$option = 's';
			}
			
			$fid = join ( '_', $info );
			
			$finfo = array ('field' => $fid, 'type' => 'string', 'var' => $field, 'default' => $val, 'required' => false, 'auto_increment' => false, 'time_update' => false );
			
			$len = ! empty ( $option ) ? strlen ( $option ) : 0;
			
			for($i = 0; $i < $len; $i ++) {
				$op = $option {$i};
				switch ($op) {
					case 'n' : //数值型,int float,long,number等
						$finfo ['type'] = 'number';
						break;
					case 's' : //类型:字符型
						$finfo ['type'] = 'string';
						break;
					case 'r' : //必须有值
						$finfo ['required'] = true;
						break;
					case 'k' : //主键
						$this->_pk [] = $fid;
						break;
					case 'a' : //自增字段
						$finfo ['auto_increment'] = true;
						break;
					case 't' : //日期时间:2010-09-09 15:00:00
						$finfo ['time_update'] = 't';
						break;
					case 'T' : //日期加时间的INT型
						$finfo ['time_update'] = 'T';
						$finfo ['type'] = 'number';
						break;
					case 'd' : //日期:2010-09-09
						$finfo ['time_update'] = 'd';
						break;
					case 'u' : //自动更新日期和时间字段
						$finfo ['auto_update'] = true;
						break;
					default :
				}
			}
			$this->_fields [$fid] = $finfo;
		}
		$keys = array_keys ( $this->_fields );
		$this->_all = '`' . join ( '`,`', $keys ) . '`';
	}
}